﻿#region FileHeader
//////////////////////////////////////////////////////////////////////////////////
// Author:                          Hector Llanos
// Date:                            03.19.2011
// FileName:                        CEnemyManager.cs
// Reason:                          enemy manager to allow flocking
//////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

using Objects;
using Base;
using Event_System;
using Run_Game;

namespace Objects
{
    public class CEnemyManager : CBase, IListener
    {
        private List<CEnemy> m_listOfEnemies;
        private static CEnemyManager m_pInstance = new CEnemyManager();

        private Vector2 m_vFocusForward;
        private Vector2 m_vFocusPoint;

        float cohesionStrength;
        float separationStrength;
        float alignmentStrength;

        public List<CEnemy> ListOfEnemies
        {
            get { return m_listOfEnemies; }
            set { m_listOfEnemies = value; }
        }
        public static CEnemyManager Instance
        {
            get { return CEnemyManager.m_pInstance; }
            set { CEnemyManager.m_pInstance = value; }
        }
        /// <summary>
        /// Value ranging from zero to one that is used to determine strength or impact of cohesion algorithm.
        /// Set to 0 to disable the cohesion algorithm.
        /// <remarks>Value is clamped to zero if less or one if greater then the allowed range.</remarks>
        /// </summary>
        public float CohesionStrength
        {
            get { return cohesionStrength; }
            set
            {
                cohesionStrength = value;
                if (cohesionStrength > 1f)
                    cohesionStrength = 1f;
                else if (cohesionStrength < 0f)
                    cohesionStrength = 0f;
            }
        }

        /// <summary>
        /// Value ranging from zero to one that is used to determine strength or impact of separation algorithm.
        /// Set to zero to disable the separation algorithm.
        /// <remarks>Value is clamped to zero if less or one if greater then the allowed range.</remarks>
        /// </summary>
        public float SeparationStrength
        {
            get { return separationStrength; }
            set
            {
                separationStrength = value;
                if (separationStrength > 1f)
                    separationStrength = 1f;
                else if (separationStrength < 0f)
                    separationStrength = 0f;
            }
        }

        /// <summary>
        /// Value ranging from zero to one that is used to determine strength or impact of alignment algorithm.
        /// Set to zero to disable the alignment algorithm.
        /// <remarks>Value is clamped to zero if less or one if greater then the allowed range.</remarks>
        /// </summary>
        public float AlignmentStrength
        {
            get { return alignmentStrength; }
            set
            {
                alignmentStrength = value;
                if (alignmentStrength > 1f)
                    alignmentStrength = 1f;
                else if (alignmentStrength < 0f)
                    alignmentStrength = 0f;
            }
        }
        CEnemyManager()
        {
            m_listOfEnemies = new List<CEnemy>();
            Game1.Instance.EventSystem.RegisterClient("Cohesion", this);
            Game1.Instance.EventSystem.RegisterClient("Alignment", this);
            Game1.Instance.EventSystem.RegisterClient("Separation", this);
        }

        ~CEnemyManager()
        {
            Game1.Instance.EventSystem.UnregisterClient("Cohesion", this);
            Game1.Instance.EventSystem.UnregisterClient("Alignment", this);
            Game1.Instance.EventSystem.UnregisterClient("Separation", this);
        }
        private void CalculateFocus()
        {
            if (m_listOfEnemies[0].Leader == true)
            {
                m_vFocusForward = m_listOfEnemies[0].GetForward();
                m_vFocusPoint = m_listOfEnemies[0].Position;
            }
            else
            {
                Vector2 averageFoward = new Vector2();
                Vector2 averagePos = new Vector2();

                for (int i = 0; i < m_listOfEnemies.Count; ++i)
                {
                    averageFoward += m_listOfEnemies[i].GetForward();
                    averagePos += m_listOfEnemies[i].Position;
                }


                averagePos.X /= (float)m_listOfEnemies.Count;
                averagePos.Y /= (float)m_listOfEnemies.Count;

                averageFoward.X /= (float)m_listOfEnemies.Count;
                averageFoward.Y /= (float)m_listOfEnemies.Count;

                m_vFocusPoint = averagePos;
                m_vFocusForward = averageFoward;
            }

        }
        /// <summary>
        /// Determines the acceleration vector to apply to a enemy based
        /// on the alignment algorithm
        /// </summary>
        /// <returns>the acceleration vector to apply</returns>
        private Vector2 GetAlignmentAcceleration()
        {
            Vector2 acceleration = new Vector2();
            if (AlignmentStrength != 0)
            {
                // TODO: Compute acceleration vector based on alignment algorithm.
                // Scale this vector by the AlignmentStrength property.

                acceleration = m_vFocusForward;
                acceleration *= alignmentStrength;

                //acceleration.X *= AlignmentStrength;
                // acceleration.Y *= AlignmentStrength;
            }
            return acceleration;
        }
        /// <summary>
        /// Determines the acceleration vector to apply to the provided 
        /// enemy based on the cohesion algorithm
        /// </summary>
        /// <param name="enemy">the provided enemy</param>
        /// <returns>the acceleration vector to apply</returns>
        private Vector2 GetCohesionAcceleration(CEnemy enemy)
        {
            Vector2 acceleration = new Vector2();
            if (CohesionStrength != 0)
            {
                // TODO: Compute acceleration vector based on cohesion algorithm.
                // Set the length of this vector to the CohesionStrength property.
                acceleration = m_vFocusPoint - enemy.Position;
                acceleration.Normalize();
                acceleration *= CohesionStrength;
            }
            return acceleration;
        }
        /// <summary>
        /// Determines the acceleration vector to apply to the provided 
        /// enemy based on the separation algorithm
        /// </summary>
        /// <param name="enemy">the provided enemy</param>
        /// <returns>the acceleration vector to apply</returns>
        private Vector2 GetSeparationAcceleration(CEnemy enemy)
        {
            Vector2 acceleration = new Vector2();
            if (SeparationStrength != 0)
            {
                int otherenemyIndex = 0;
                if (PredictCollision(enemy, out otherenemyIndex))
                {
                    int neighbor = 0;
                    Vector2 fromNeighbor = new Vector2();

                    // Determine nearest neighbor
                    GetNearestEnemyDistance(enemy, out neighbor);

                    //push away vector
                    fromNeighbor = enemy.Position - m_listOfEnemies[neighbor].Position;
                    fromNeighbor.Normalize();

                    acceleration = fromNeighbor;
                    acceleration *= SeparationStrength;
                }
            }
            return acceleration;
        }
        /// <summary>
        /// Determines the closest enemy to the provided enemy's position and the distance between the two
        /// </summary>
        /// <param name="fromThisEnemy">the provided enemy</param>
        /// <param name="closestEnemyIndex">OUT PARAM: the index (NOT ID) of the closest enemy</param>
        /// <returns>the distance from the provided enemy to the closest enemy</returns>
        /// <remarks>this will return zero and set otherenemyIndex to zero if only one enemy is present in this TaskForce</remarks>
        private float GetNearestEnemyDistance(CEnemy fromThisEnemy, out int closestEnemyIndex)
        {
            if (m_listOfEnemies.Count < 2)
            {
                closestEnemyIndex = 0;
                return 0;
            }
            Vector2 distanceVector = new Vector2();
            float distanceSquared, temp;
            if (m_listOfEnemies[0].Type == fromThisEnemy.Type)
            {
                distanceVector = m_listOfEnemies[1].Position - fromThisEnemy.Position;
                distanceSquared = distanceVector.Length() * distanceVector.Length();
                closestEnemyIndex = 1;
            }
            else
            {
                distanceVector = m_listOfEnemies[0].Position - fromThisEnemy.Position;
                distanceSquared = distanceVector.Length() * distanceVector.Length();
                closestEnemyIndex = 0;
            }
            for (int i = 0; i < m_listOfEnemies.Count; ++i)
            {
                if (m_listOfEnemies[i].Type == fromThisEnemy.Type)
                    continue;
                distanceVector = m_listOfEnemies[i].Position - fromThisEnemy.Position;
                temp = distanceVector.Length() * distanceVector.Length();
                if (temp < distanceSquared)
                {
                    distanceSquared = temp;
                    closestEnemyIndex = i;
                }
            }
            return Convert.ToSingle(System.Math.Sqrt(distanceSquared));
        }
        /// <summary>
        /// Determines if the provided enemy will collide or is colliding with the closest enemy
        /// </summary>
        /// <param name="enemy">the provided enemy</param>
        /// <param name="closestenemyIndex">OUT PARAM: the index (NOT ID) of the closest enemy</param>
        /// <returns>true if the provided enemy will collide or is colliding with the closest enemy</returns>
        /// <remarks>this will return false and set otherenemyIndex to zero if only one enemy is present in this TaskForce</remarks>
        private bool PredictCollision(CEnemy enemy, out int otherenemyIndex)
        {
            if (m_listOfEnemies.Count < 2)
            {
                otherenemyIndex = 0;
                return false;
            }
            float distance = GetNearestEnemyDistance(enemy, out otherenemyIndex);
            return (distance <= m_listOfEnemies[otherenemyIndex].Width * 3f);
        }



        public override void Update(float fElapsedTime)
        {
            //cohesionStrength -= 0.1f;
            //separationStrength -= 0.1f;
            //alignmentStrength -= 0.1f;

            if (cohesionStrength > 1f)
                cohesionStrength = 1f;
            else if (cohesionStrength < 0f)
                cohesionStrength = 0f;

            if (separationStrength > 1f)
                separationStrength = 1f;
            else if (separationStrength < 0f)
                separationStrength = 0f;

            if (alignmentStrength > 1f)
                alignmentStrength = 1f;
            else if (alignmentStrength < 0f)
                alignmentStrength = 0f;

            if (m_listOfEnemies.Count > 0)
            {
                // TODO: Calculate the flock focus.
                CalculateFocus();

                for (int i = 0; i < m_listOfEnemies.Count; ++i)
                {
                    if (!(m_listOfEnemies[i].Leader))
                    {
                        Vector2 accumulator = new Vector2();

                        // TODO: Use accumulator to calculate the sum of each
                        // steering behavior acceleration vector.  Scale this
                        // sum by the product of the ship's task force speed 
                        // and time. Then add this accumulator to the ship's 
                        // velocity vector.

                        accumulator += GetAlignmentAcceleration();
                        accumulator += GetCohesionAcceleration(m_listOfEnemies[i]);
                        accumulator += GetSeparationAcceleration(m_listOfEnemies[i]);

                        //Scale this sum by the product of the ship's task force speed and time
                        //accumulator.Normalize();

                        accumulator *= (m_listOfEnemies[i].Speed * fElapsedTime);
                        m_listOfEnemies[i].Velocity += accumulator;

                    }
                    // m_listOfEnemies[i].Update(fElapsedTime);
                }
            }
        }

        public void HandleEvent(CEvent pEvent)
        {
            if (pEvent.EventID == "Cohesion" && this == pEvent.Param)
            {
                cohesionStrength += 0.2f;
            }
            if (pEvent.EventID == "Alignment" && this == pEvent.Param)
            {
                alignmentStrength += 0.2f;
            }
            if (pEvent.EventID == "Separation" && this == pEvent.Param)
            {
                separationStrength += 0.2f;
            }
            if (pEvent.EventID == "Reset" && this == pEvent.Param)
            {
                cohesionStrength = 0.0f;
                alignmentStrength = 0.0f;
                separationStrength = 0.0f;

                for (int i = 1; i < m_listOfEnemies.Count; ++i)
                {
                    m_listOfEnemies[i].Position = new Vector2( 500 + (i * 10), 300);
                }

            }

        }

    }
}
